package control.aspect;

import base.standard.auth.AuthLogic;
import base.standard.auth.NeedAuth;
import base.standard.auth.NeedRole;
import base.standard.constant.ErrorCode;
import base.standard.constant.ThreadLocalParams;
import base.standard.exception.SimpleException;
import base.util.BeanUtil;
import control.authcore.IAuth;
import control.authcore.IAuthData;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * Auth aspect
 *
 * @description: 鉴权切面
 * @author: cyj
 * @date 2020 -10-21 20:36:23
 */
@Aspect
@Component
@Order(1)
public class AuthAspect {

    private IAuth iAuth = this::auth;

    @Autowired
    private IAuthData authData;

    /**
     * Role aspect role切入点
     */
    @Pointcut(value = "@annotation(base.standard.auth.NeedRole)")
    public void roleAspect() {
    }

    /**
     * Auth aspect auth切入点
     */
    @Pointcut(value = "@annotation(base.standard.auth.NeedAuth)")
    public void authAspect() {
    }

    /**
     * Do role *
     *
     * @param joinPoint join point
     */
    @Before("roleAspect()")
    public void doRole(JoinPoint joinPoint) {
        Method method = BeanUtil.getMethod(joinPoint);
        Optional.ofNullable(method.getAnnotation(NeedRole.class)).ifPresent(needRole -> {
            String[] needRoles = needRole.roles();
            AuthLogic authLogic = needRole.authLogic();
            ThreadLocalParams.setAuthMessage(getMessage(needRoles, authLogic));
            iAuth.auth(authData.offerRoles(), needRoles, authLogic, ErrorCode.PERMISSION_DENIED_ROLE);
        });
    }

    /**
     * Do auth *
     *
     * @param joinPoint join point
     */
    @Before("authAspect()")
    public void doAuth(JoinPoint joinPoint) {
        Method method = BeanUtil.getMethod(joinPoint);
        Optional.ofNullable(method.getAnnotation(NeedAuth.class)).ifPresent(needRole -> {
            String[] needAuths = needRole.auths();
            AuthLogic authLogic = needRole.authLogic();
            ThreadLocalParams.setAuthMessage(getMessage(needAuths, authLogic));
            iAuth.auth(authData.offerAuths(), needAuths, authLogic, ErrorCode.PERMISSION_DENIED_AUTH);
        });
    }

    private void auth(List<String> objects, String[] needObjects, AuthLogic authLogic, ErrorCode errorCode) {
        boolean auth = false;
        List<String> needs = Arrays.asList(needObjects).stream().distinct().filter(needObject -> null != needObject)
                .collect(Collectors.toList());
        if (AuthLogic.OR == authLogic) {
            auth = (0 < needs.stream().filter(objects::contains).count());
        } else if (AuthLogic.AND == authLogic) {
            auth = objects.containsAll(needs);
        }
        if (!auth) {
            throw new SimpleException(errorCode, ThreadLocalParams.getAuthMessage());
        }
    }

    private String getMessage(String[] objects, AuthLogic authLogic) {
        StringBuffer message = new StringBuffer();
        IntStream.range(0, objects.length).forEach(i -> {
            if (i == objects.length - 1) {
                message.append(objects[i]);
            } else {
                message.append(objects[i]).append(" ").append(authLogic).append(" ");
            }
        });
        return message.toString();
    }
}
